/**
 * @file STL_URL.cpp
 * @author Derek Liu (yjkhtddx@sina.com)
 * @brief 
 * @version 0.1
 * @date 2020-09-15
 * 
 * @copyright Copyright Sci-Tech Laboratories (c) 2020
 * 
 */

#include "STL_URL.h"
#include <netinet/in.h> // htons, htonl, ntons, ntonl
#include <iostream>

namespace STL
{
    uint16_t STL_URL::defaultPort(std::string protocol)
    {
        if (protocol.empty())
            return 0;
        if (protocol == "http")
            return 80;
        if (protocol == "https")
            return 443;
        if (protocol == "rtsp")
            return 554;
        if (protocol == "rtmp")
            return 1935;
        if (protocol == "hls")
            return 80;
        if (protocol == "ftp")
            return 21;
        if (protocol == "ssh")
            return 22;
        if (protocol == "smtp")
            return 25;
        if (protocol == "pop3")
            return 110;
        if (protocol == "oracle")
            return 1521;
        if (protocol == "mysql")
            return 3306;
        if (protocol == "telnet")
            return 23;
        if (protocol == "sqlserver")
            return 1443;
        if (protocol == "redis")
            return 6379;
        return 0;
    }

    unsigned char UrlEncodeToHex(unsigned char x)
    {
        return x > 9 ? x + 55 : x + 48;
    }

    unsigned char UrlDecodeFromHex(unsigned char x)
    {
        unsigned char y = 0;
        if (x >= 'A' && x <= 'Z')
            y = x - 'A' + 10;
        else if (x >= 'a' && x <= 'z')
            y = x - 'a' + 10;
        else if (x >= '0' && x <= '9')
            y = x - '0';
        // else
        //     assert(0);
        return y;
    }
    // W3C标准规定:当Content-Type为application/x-www-form-urlencoded时，URL中查询参数名和参数值中空格要用加号+替代
    // RFC2396 定义URI: URI里的保留字符都需转义成%HH格式(Section 3.4 Query Component)，因此空格会被编码成%20，加号+本身也作为保留字而被编成%2B
    // 此处不应该把' '转为'+'
    /*
    3.4. Query Component

        The query component is a string of information to be interpreted by
        the resource.

            query         = *uric

        Within a query component, the characters ";", "/", "?", ":", "@",
        "&", "=", "+", ",", and "$" are reserved.
    */
    std::string STL_URL::UrlEncode(const std::string str)
    {
        std::string strTemp = "";
        size_t length = str.length();
        for (size_t i = 0; i < length; i++)
        {
            if ((str[i] == ';') ||
                (str[i] == '/') ||
                (str[i] == '?') ||
                (str[i] == ':') ||
                (str[i] == '@') ||
                (str[i] == '&') ||
                (str[i] == '=') ||
                (str[i] == '+') ||
                (str[i] == ',') ||
                (str[i] == '$') ||
                (str[i] == ' '))
            {
                strTemp += '%';
                strTemp += UrlEncodeToHex((unsigned char)str[i] >> 4);
                strTemp += UrlEncodeToHex((unsigned char)str[i] % 16);
            }
            else
            {
                strTemp += str[i];
            }
            /* 过时旧版本逻辑
            //int isalnum ( int c ); 检查字符是否为字母数字
            //int isalpha ( int c ); 检查字符是否为字母
            //int isdigit ( int c ); 检查字符是否为数字
            if (isalnum((unsigned char)str[i]) ||
                (str[i] == '-') ||
                (str[i] == '_') ||
                (str[i] == '.') ||
                (str[i] == '~'))
                strTemp += str[i];
            else if (str[i] == ' ')
                strTemp += "+";
            else
            {
                strTemp += '%';
                strTemp += UrlEncodeToHex((unsigned char)str[i] >> 4);
                strTemp += UrlEncodeToHex((unsigned char)str[i] % 16);
            }
            */
        }
        return strTemp;
    }

    std::string STL_URL::UrlDecode(const std::string str)
    {
        std::string strTemp = "";
        size_t length = str.length();
        for (size_t i = 0; i < length; i++)
        {
            if (str[i] == '+')
                strTemp += ' ';
            else if (str[i] == '%')
            {
                // assert(i + 2 < length);
                unsigned char high = UrlDecodeFromHex((unsigned char)str[++i]);
                unsigned char low = UrlDecodeFromHex((unsigned char)str[++i]);
                strTemp += high * 16 + low;
            }
            else
                strTemp += str[i];
        }
        return strTemp;
    }

    std::string STL_URL::genUrl(STL_URL::UrlInfo info)
    {
        //[<protocol>://][<username>[:<password>]@]<host>[:<port>][/<path>][;<parameter>][?<query>][#<fragment>]
        std::string ret = "";
        if (!info.protocol.empty())
        {
            ret += info.protocol + "://";
        }
        if (!info.username.empty())
        {
            ret += info.username;
            if (!info.password.empty())
            {
                ret += ":" + info.password;
            }
            ret += "@";
        }
        if (info.host.empty())
        {
            return "";
        }
        else
        {
            ret += info.host;
        }
        if (info.port != 0)
        {
            ret += ":" + std::to_string(info.port);
        }
        if (info.path.empty())
        {
            ret += "/";
        }
        else
        {
            if (info.path[0] == '/')
            {
                ret += info.path;
            }
            else
            {
                ret += "/" + info.path;
            }
        }
        if (!info.parameter.empty())
        {
            ret += ";" + info.parameter;
        }
        if (!info.query.empty())
        {
            ret += "?" + info.query;
        }
        if (!info.fragment.empty())
        {
            ret += "#" + info.fragment;
        }
        return ret;
    }

    STL_URL::UrlInfo STL_URL::parseUrl(std::string url)
    {
        /*
        typedef struct UrlInfo_t
        {
            std::string protocol = "";  // 协议
            std::string username = "";  // 协议
            std::string password = "";  // 协议
            std::string host = "";  // 主机地址
            uint16_t port = 0;      // 端口
            std::string path = "";      // 路径
            std::string parameter = ""; // 参数
            std::string query = "";     // 查询字符串
            std::string fragment = "";  // 片段
        } UrlInfo;
        //[<protocol>://][<username>[:<password>]@]<host>[:<port>][/<path>][;<parameter>][?<query>][#<fragment>]
        */
        STL_URL::UrlInfo ret;
        size_t pos = 0;
        std::string flag;

        // protocol 协议
        size_t start_protocol = pos;
        size_t end_protocol = url.find("://", pos);
        if (end_protocol != std::string::npos)
        {
            ret.protocol = url.substr(start_protocol, end_protocol - start_protocol);
            ret.port = defaultPort(ret.protocol);
            pos = end_protocol + 3;
        }

        // username password 用户名密码
        size_t start_username_password = pos;
        size_t end_username_password = url.find("@", pos);
        if (end_username_password != std::string::npos)
        {
            std::string username_password = url.substr(start_username_password, end_username_password - start_username_password);
            size_t pos_temp = 0;
            // username 用户名
            size_t start_username = pos_temp;
            size_t end_username = username_password.find(":", pos_temp);
            if (end_username != std::string::npos)
            {
                ret.username = username_password.substr(start_username, end_username - start_username);
                pos_temp = end_username + 1;
                //password 密码
                ret.password = username_password.substr(pos_temp);
            }
            else
            {
                ret.username = username_password;
            }
            pos = end_username_password + 1;
        }
        //host 主机地址
        size_t start_host = pos;
        size_t end_host = url.find_first_of(":/;?#", pos);

        if (end_host != std::string::npos)
        {
            ret.host = url.substr(start_host, end_host - start_host);
            flag = url.substr(end_host, 1);
            pos = end_host + 1;
        }
        else
        {
            ret.host = url.substr(start_host);
            return ret;
        }
        if (flag == ":")
        {
            size_t start_port = pos;
            size_t end_port = url.find_first_of("/;?#", pos);
            if (end_port != std::string::npos)
            {
                std::string port = url.substr(start_port, end_port - start_port);
                if (!port.empty())
                    ret.port = stoul(port);
                flag = url.substr(end_port, 1);
                pos = end_port + 1;
            }
            else
            {
                std::string port = url.substr(start_port);
                if (!port.empty())
                    ret.port = stoul(port);
                return ret;
            }
        }
        if (flag == "/")
        {
            size_t start_path = pos - 1;
            size_t end_path = url.find_first_of(";?#", pos);
            if (end_path != std::string::npos)
            {
                ret.path = url.substr(start_path, end_path - start_path);
                flag = url.substr(end_path, 1);
                pos = end_path + 1;
            }
            else
            {
                ret.path = url.substr(start_path);
                return ret;
            }
        }
        if (flag == ";")
        {
            size_t start_parameter = pos;
            size_t end_parameter = url.find_first_of("?#", pos);
            if (end_parameter != std::string::npos)
            {
                ret.parameter = url.substr(start_parameter, end_parameter - start_parameter);
                flag = url.substr(end_parameter, 1);
                pos = end_parameter + 1;
            }
            else
            {
                ret.parameter = url.substr(start_parameter);
                return ret;
            }
        }
        if (flag == "?")
        {
            size_t start_query = pos;
            size_t end_query = url.find_first_of("#", pos);
            if (end_query != std::string::npos)
            {
                ret.query = url.substr(start_query, end_query - start_query);
                flag = url.substr(end_query, 1);
                pos = end_query + 1;
            }
            else
            {
                ret.query = url.substr(start_query);
                return ret;
            }
        }
        ret.fragment = url.substr(pos);
        return ret;
    }
} // namespace STL
